<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="迎着朝阳的博客" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    3、Spring 数据验证 |  迎着朝阳
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="https://dxysun.com/static/yan.png" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  
<script>
var _hmt = _hmt || [];
(function() {
	var hm = document.createElement("script");
	hm.src = "https://hm.baidu.com/hm.js?aa994a8d65700b8835787dd39d079d7e";
	var s = document.getElementsByTagName("script")[0]; 
	s.parentNode.insertBefore(hm, s);
})();
</script>


</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-springForvalidator"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  3、Spring 数据验证
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/12/13/springForvalidator/" class="article-date">
  <time datetime="2020-12-13T08:22:35.000Z" itemprop="datePublished">2020-12-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/springdocs/">spring官方文档</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">10.2k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">41 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p><a id="validation"></a></p>
<a id="more"></a>

<h2 id="3-验证-数据绑定和类型转换"><a href="#3-验证-数据绑定和类型转换" class="headerlink" title="3. 验证, 数据绑定和类型转换"></a><a href="#validation"></a>3. 验证, 数据绑定和类型转换</h2><p>在业务逻辑中添加数据验证利弊参半，Spring提供的验证(和数据绑定）方案也未必能解决这个问题。具体来说，验证不应该与Web层绑定，并且应该易于本地化，并且应该可以插入任何可用的验证器。 考虑到这些问题，Spring提出了一个基本的，非常有用的<code>Validator</code>接口，它在应用程序的每一层都可用。</p>
<p>Spring为开发者提供了一个称作<code>DataBinder</code>的对象来处理数据绑定，所谓的数据绑定就是将用户输入自动地绑定到相应的领域模型（或者说用来处理用户所输入的任何对象）。Spring的<code>Validator</code>和<code>DataBinder</code> 构成了验证包，这个包主要用在Spring MVC框架使用，但绝不限于此。</p>
<p><code>BeanWrapper</code>是Spring框架中的一个基本概念，并且在很多地方使用。但是，在使用过程中可能从未碰过它。鉴于这是一份参考文档，那么对<code>BeanWrapper</code>的解释很有必要。 我们将在本章中解释<code>BeanWrapper</code>，在开发者尝试将数据绑定到对象的时候一定会使用到它。</p>
<p>Spring的<code>DataBinder</code> 和底层的<code>BeanWrapper</code>都使用<code>PropertyEditorSupport</code>实现来解析和格式化属性值。 <code>PropertyEditor</code>和<code>PropertyEditorSupport</code>接口是JavaBeans规范的一部分，本章也对其进行了解释。 Spring 3引入了<code>core.convert</code>包来提供通用的类型转换工具和高级 “format” 包来格式化UI显示。这两个包提供的工具可以用作<code>PropertyEditorSupport</code> 的替代品，这一章也会讨论到它们。</p>
<p>JSR-303/JSR-349 Bean 验证</p>
<p>从Spring4.0开始，Spring框架支持Bean Validation 1.0（JSR-303）和Bean Validation 1.1（JSR-349）验证规范，同时也能兼容Spring的<code>Validator</code>验证接口。</p>
<p>应用程序既可以一次性开启控制全局的Bean Validation，如<a href="#validation-beanvalidation">Spring Validation</a>中所述，并专门用于所有验证需求。</p>
<p>应用程序还可以为每个<code>DataBinder</code>实例注册其他Spring <code>Validator</code> 实例，如配置<a href="#validation-binder">Configuring a <code>DataBinder</code></a>中所述。</p>
<p><a id="validator"></a></p>
<h3 id="3-1-使用Spring的Validator接口来进行数据验证"><a href="#3-1-使用Spring的Validator接口来进行数据验证" class="headerlink" title="3.1. 使用Spring的Validator接口来进行数据验证"></a><a href="#validator"></a>3.1. 使用Spring的Validator接口来进行数据验证</h3><p>Spring提供了<code>Validator</code>接口用来进行对象的数据验证。<code>Validator</code>接口在进行数据验证的时候会要求传入一个<code>Errors</code> 对象，当有错误产生时会将错误信息放入该对象。</p>
<p>考虑以下小数据对象的示例：:</p>
<pre><code>public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}</code></pre><p>下一个示例通过实现<code>org.springframework.validation.Validator</code> 接口的以下两个方法为<code>Person</code>类提供验证行为：</p>
<ul>
<li><p><code>supports(Class)</code>: 判断该<code>Validator</code>是否能验证提供的<code>Class</code>的实例?</p>
</li>
<li><p><code>validate(Object, org.springframework.validation.Errors)</code>: 验证给定的对象，如果有验证失败信息，则将其放入<code>Errors</code> 对象。</p>
</li>
</ul>
<p>实现一个<code>Validator</code>相当简单，尤其是使用Spring提供的<code>ValidationUtils</code>工具类时。以下示例为Person实例实现Validator：</p>
<pre><code>public class PersonValidator implements Validator {

    /**
     * This Validator validates *only* Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, &quot;name&quot;, &quot;name.empty&quot;);
        Person p = (Person) obj;
        if (p.getAge() &lt; 0) {
            e.rejectValue(&quot;age&quot;, &quot;negativevalue&quot;);
        } else if (p.getAge() &gt; 110) {
            e.rejectValue(&quot;age&quot;, &quot;too.darn.old&quot;);
        }
    }
}</code></pre><p>ValidationUtils中的静态方法<code>rejectIfEmpty(..)</code>方法用于拒绝 <code>name</code>属性（如果它为<code>null</code> 或空字符串）。更多信息可以参考<a href="https://docs.spring.io/spring-framework/docs/5.1.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/validation/ValidationUtils.html" target="_blank" rel="noopener"><code>ValidationUtils</code></a>的javadocs。</p>
<p>虽然可以实现一个<code>Validator</code>类来验证富对象中的每个嵌套对象，但最好将每个嵌套对象类的验证逻辑封装在自己的<code>Validator</code>实现中。 例如，有一个名为<code>Customer</code>的复杂对象，它有两个<code>String</code>类型的属性（first name和second name），另外还有一个<code>Address</code>对象。它与<code>Customer</code>毫无关系， 它还实现了名为<code>AddressValidator</code>的验证器。如果考虑在<code>Customer</code>验证器类中重用<code>Address</code>验证器的功能（这种重用不是通过简单的代码拷贝）， 那么可以将<code>Address</code>验证器的实例通过依赖注入的方式注入到<code>Customer</code>验证器中。如以下示例所示：</p>
<pre><code>public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException(&quot;The supplied [Validator] is &quot; +
                &quot;required and must not be null.&quot;);
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException(&quot;The supplied [Validator] must &quot; +
                &quot;support the validation of [Address] instances.&quot;);
        }
        this.addressValidator = addressValidator;
    }

    /**
     * This Validator validates Customer instances, and any subclasses of Customer too
     */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, &quot;firstName&quot;, &quot;field.required&quot;);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, &quot;surname&quot;, &quot;field.required&quot;);
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath(&quot;address&quot;);
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}</code></pre><p>验证错误信息会上报给作为参数传入的<code>Errors</code>对象，如果使用Spring Web MVC。您可以使用<code>&lt;spring:bind/&gt;</code>标记来检查错误消息，但您也可以自己检查<code>Errors</code>对象。 有关它提供的方法的更多信息可以在<a href="https://docs.spring.io/spring-framework/docs/5.1.3.BUILD-SNAPSHOT/javadoc-api/org/springframeworkvalidation/Errors.html" target="_blank" rel="noopener">javadoc</a>中找到。</p>
<p><a id="validation-conversion"></a></p>
<h3 id="3-2-通过错误编码得到错误信息"><a href="#3-2-通过错误编码得到错误信息" class="headerlink" title="3.2. 通过错误编码得到错误信息"></a><a href="#validation-conversion"></a>3.2. 通过错误编码得到错误信息</h3><p>上一节介绍了数据绑定和数据验证，如何拿到验证错误信息是最后需要讨论的问题。在<a href="#validator">上一个</a>的例子中，验证器拒绝了<code>name</code>和<code>age</code> 属性。如果我们想通过使用<code>MessageSource</code>输出错误消息， 可以在验证失败时设置错误编码（本例中就是name和age）。当调用（直接或间接地，通过使用 <code>ValidationUtils</code>类）<code>Errors</code>接口中的<code>rejectValue</code>方法或者它的任意一个方法时，它的实现不仅仅注册传入的错误编码参数， 还会注册一些遵循一定规则的错误编码。注册哪些规则的错误编码取决于开发者使用的<code>MessageCodesResolver</code>。当使用默认的<code>DefaultMessageCodesResolver</code>时， 除了会将错误信息注册到指定的错误编码上，这些错误信息还会注册到包含属性名的错误编码上。假如调用<code>rejectValue(&quot;age&quot;, &quot;too.darn.old&quot;)</code>方法，Spring除了会注册<code>too.darn.old</code>错误编码外， 还会注册<code>too.darn.old.age</code>和<code>too.darn.old.age.int</code>这两个错误编码（即一个是包含属性名，另外一个既包含属性名还包含类型的）。在Spring中这种注册称为注册约定，这样所有的开发者都能按照这种约定来定位错误信息。</p>
<p>有关<code>MessageCodesResolver</code>和默认策略的更多信息可分别在 <a href="https://docs.spring.io/spring-framework/docs/5.1.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/validation/MessageCodesResolver.html" target="_blank" rel="noopener"><code>MessageCodesResolver</code></a> 和 <a href="https://docs.spring.io/spring-framework/docs/5.1.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/validation/DefaultMessageCodesResolver.html" target="_blank" rel="noopener"><code>DefaultMessageCodesResolver</code></a>, 的javadoc中找到.</p>
<p><a id="beans-beans"></a></p>
<h3 id="3-3-操作bean和BeanWrapper"><a href="#3-3-操作bean和BeanWrapper" class="headerlink" title="3.3. 操作bean和BeanWrapper"></a><a href="#beans-beans"></a>3.3. 操作bean和<code>BeanWrapper</code></h3><p><code>org.springframework.beans</code>包遵循Oracle提供的JavaBeans标准，JavaBean只是一个包含默认无参构造器的类，它遵循命名约定（举例来说） 名为 <code>bingoMadness</code>属性将拥有设置方法 <code>setBingoMadness(..)</code>和获取方法<code>getBingoMadness()</code>。有关JavaBeans和规范的更多信息，请参考Oracle的网站(<a href="https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html" target="_blank" rel="noopener">javabeans</a>）。</p>
<p>beans包里一个非常重要的类是<code>BeanWrapper</code>接口和它的相应实现(<code>BeanWrapperImpl</code>)。引自java文档：<code>BeanWrapper</code>提供了设置和获取属性值(单独或批量）、 获取属性描述符以及查询属性以确定它们是可读还是可写的功能。 <code>BeanWrapper</code>还提供对嵌套属性的支持，能够不受嵌套深度的限制启用子属性的属性设置。<code>BeanWrapper</code>还提供了无需目标类代码的支持就能够添加标准JavaBeans的 <code>PropertyChangeListeners</code>和<code>VetoableChangeListeners</code>的能力。 最后但同样重要的是， <code>BeanWrapper</code>支持设置索引属性。应用程序代码通常不会直接使用<code>BeanWrapper</code>，而是提供给<code>DataBinder</code>和<code>BeanFactory</code>使用。</p>
<p><code>BeanWrapper</code> 顾名思义，它包装了bean并对其执行操作。例如设置和获取属性。</p>
<p><a id="beans-beans-conventions"></a></p>
<h4 id="3-3-1-设置并获取基本和嵌套的属性"><a href="#3-3-1-设置并获取基本和嵌套的属性" class="headerlink" title="3.3.1. 设置并获取基本和嵌套的属性"></a><a href="#beans-beans-conventions"></a>3.3.1. 设置并获取基本和嵌套的属性</h4><p>设置和获取属性是通过使用<code>setPropertyValue</code>,<code>setPropertyValues</code>, <code>getPropertyValue</code>, 和 <code>getPropertyValues</code>方法完成的，这些方法带有多个重载变体。 Springs javadoc更详细地描述了它们。 JavaBeans规范具有指示对象属性的约定。 下表显示了这些约定的一些示例：</p>
<p>Table 11. Examples of properties</p>
<table>
<thead>
<tr>
<th>Expression</th>
<th>Explanation</th>
</tr>
</thead>
<tbody><tr>
<td><code>name</code></td>
<td>表示属性 <code>name</code>与<code>getName()</code>或<code>isName()</code>和<code>setName(..)</code>方法相对应</td>
</tr>
<tr>
<td><code>account.name</code></td>
<td>表示 <code>account</code> 属性的嵌套属性<code>name</code>与<code>getAccount().setName()</code> 或 <code>getAccount().getName()</code> 相对应.</td>
</tr>
<tr>
<td><code>account[2]</code></td>
<td>表示索引属性<code>account</code>的第<em>3</em>个属性. 索引属性可以是<code>array</code>, <code>list</code>, 其他自然排序的集合.</td>
</tr>
<tr>
<td><code>account[COMPANYNAME]</code></td>
<td>表示映射属性<code>account</code>被键<code>COMPANYNAME</code> 索引的映射项的值。</td>
</tr>
</tbody></table>
<p>（如果您不打算直接使用<code>BeanWrapper</code> ，那么下一节对您来说并不重要。如果您只使用<code>DataBinder</code>和<code>BeanFactory</code>及其默认实现，那么您应该跳到<a href="#beans-beans-conversion">有关</a><code>PropertyEditors</code>的部分。）</p>
<p>以下两个示例类使用<code>BeanWrapper</code>来获取和设置属性：</p>
<pre><code>public class Company {

    private String name;
    private Employee managingDirector;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Employee getManagingDirector() {
        return this.managingDirector;
    }

    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}

public class Employee {

    private String name;

    private float salary;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }
}</code></pre><p>以下代码段显示了如何检索和操作实例化<code>Companies</code>和 <code>Employees</code>的某些属性的一些示例：</p>
<pre><code>BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue(&quot;name&quot;, &quot;Some Company Inc.&quot;);
// ... can also be done like this:
PropertyValue value = new PropertyValue(&quot;name&quot;, &quot;Some Company Inc.&quot;);
company.setPropertyValue(value);

// ok, let&apos;s create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue(&quot;name&quot;, &quot;Jim Stravinsky&quot;);
company.setPropertyValue(&quot;managingDirector&quot;, jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue(&quot;managingDirector.salary&quot;);</code></pre><p><a id="beans-beans-conversion"></a></p>
<h4 id="3-3-2-内置PropertyEditor实现"><a href="#3-3-2-内置PropertyEditor实现" class="headerlink" title="3.3.2. 内置PropertyEditor实现"></a><a href="#beans-beans-conversion"></a>3.3.2. 内置<code>PropertyEditor</code>实现</h4><p>Spring使用 <code>PropertyEditor</code>的概念来实现 <code>Object</code>和<code>String</code>之间的转换，有时使用不同于对象本身的方式来表示属性显得更方便。例如，<code>Date</code>可以使用易于阅读的方式(如<code>String</code>: <code>&#39;2007-14-09&#39;</code>）。 还能将易于阅读的形式转换回原来的<code>Date</code>(甚至做得更好：转换任何以易于阅读形式输入的日期，然后返回日期对象）。可以通过注册<code>java.beans.PropertyEditor</code>类型的自定义编辑器来实现此行为。 在<code>BeanWrapper</code>上注册自定义编辑器，或者在特定的IoC容器中注册自定义编辑器（如前一章所述），使其了解如何将属性转换为所需类型。 有关<code>PropertyEditor</code>的更多信息，请参阅<a href="https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html" target="_blank" rel="noopener">Oracle的<code>java.beans</code>包的javadoc</a></p>
<p>在Spring中使用属性编辑的几个示例:</p>
<ul>
<li><p>通过使用<code>PropertyEditor</code>实现来设置bean的属性。 当您使用<code>java.lang.String</code>作为您在XML文件中声明的某个bean的属性的值时， Spring将(如果相应属性的setter具有类参数）使用<code>ClassEditor</code>尝试将参数解析为类对象。</p>
</li>
<li><p>在Spring的MVC框架中解析HTTP请求参数是通过使用各种<code>PropertyEditor</code>实现来完成的，您可以在<code>CommandController</code>的所有子类中手动绑定它们。</p>
</li>
</ul>
<p>Spring内置了许多<code>PropertyEditor</code>用于简化处理。它们都位于<code>org.springframework.beans.propertyeditors</code>包中。大多数（但不是全部，如下表所示）默认情况下由<code>BeanWrapperImpl</code>注册。 当属性编辑器以某种方式进行配置时，开发者仍可以注册自定义的变体用于覆盖默认的变量。下表描述了Spring提供的各种<code>PropertyEditor</code>实现：</p>
<p>Table 12. 内置<code>PropertyEditor</code> 实现</p>
<table>
<thead>
<tr>
<th>类</th>
<th>解释</th>
</tr>
</thead>
<tbody><tr>
<td><code>ByteArrayPropertyEditor</code></td>
<td>字节数组的编辑器。 将字符串转换为其对应的字节表示形式。<code>BeanWrapperImpl</code>默认注册。</td>
</tr>
<tr>
<td><code>ClassEditor</code></td>
<td>将表示类的字符串解析为实际的类，反之亦然。 找不到类时，抛出<code>IllegalArgumentException</code>。 默认情况下，由<code>BeanWrapperImpl</code>注册。</td>
</tr>
<tr>
<td><code>CustomBooleanEditor</code></td>
<td><code>Boolean</code>属性的可自定义属性编辑器。 默认情况下，由<code>BeanWrapperImpl</code>注册，但可以通过将其自定义实例注册为自定义编辑器来覆盖。</td>
</tr>
<tr>
<td><code>CustomCollectionEditor</code></td>
<td><code>Collection</code>的属性编辑器，将任何源<code>Collection</code>转换为给定的目标<code>Collection</code>类型。</td>
</tr>
<tr>
<td><code>CustomDateEditor</code></td>
<td><code>java.util.Date</code>的可自定义属性编辑器，支持自定义<code>DateFormat</code>。 未默认注册。 必须根据需要使用适当的格式进行用户注册。</td>
</tr>
<tr>
<td><code>CustomNumberEditor</code></td>
<td>任何<code>Number</code> 子类的可自定义属性编辑器，例如<code>Integer</code>, <code>Long</code>, <code>Float</code>或<code>Double</code>。 默认情况下，由<code>BeanWrapperImpl</code>注册，但可以通过将其自定义实例注册为自定义编辑器来覆盖。</td>
</tr>
<tr>
<td><code>FileEditor</code></td>
<td>将字符串解析为<code>java.io.File</code>对象。 默认情况下，由<code>BeanWrapperImpl</code>注册。</td>
</tr>
<tr>
<td><code>InputStreamEditor</code></td>
<td>单向属性编辑器，可以获取字符串并生成（通过中间<code>ResourceEditor</code>和<code>Resource</code>）<code>InputStream</code>，以便<code>InputStream</code>属性可以直接设置为字符串。 请注意，默认用法不会为您关闭 <code>InputStream</code>。 默认情况下，由 <code>BeanWrapperImpl</code>注册。</td>
</tr>
<tr>
<td><code>LocaleEditor</code></td>
<td>可以将字符串解析为<code>Locale</code>对象，反之亦然（字符串格式为<code>_[country]_[variant]</code>，与<code>Locale</code>的 <code>toString()</code> 方法相同）。 默认情况下，由<code>BeanWrapperImpl</code>注册。</td>
</tr>
<tr>
<td><code>PatternEditor</code></td>
<td>可以将字符串解析为<code>java.util.regex.Pattern</code>对象，反之亦然。</td>
</tr>
<tr>
<td><code>PropertiesEditor</code></td>
<td>可以将字符串（使用 <code>java.util.Properties</code>类的javadoc中定义的格式进行格式化）转换为 <code>Properties</code> 对象。 默认情况下，由<code>BeanWrapperImpl</code>注册。</td>
</tr>
<tr>
<td><code>StringTrimmerEditor</code></td>
<td>修剪字符串的属性编辑器。 （可选）允许将空字符串转换为空值。 默认情况下未注册 - 必须是用户注册的。</td>
</tr>
<tr>
<td><code>URLEditor</code></td>
<td>可以将URL的字符串表示形式解析为实际的<code>URL</code> 对象。 默认情况下，由<code>BeanWrapperImpl</code>注册。</td>
</tr>
</tbody></table>
<p>Spring使用<code>java.beans.PropertyEditorManager</code>设置属性编辑器（可能需要）的搜索路径。搜索路径还包括 <code>sun.bean.editors</code>，其中包括<code>Font</code>, <code>Color</code>和大多数基本类型等类型的<code>PropertyEditor</code>实现。 注意，标准的JavaBeans架构可以自动发现 <code>PropertyEditor</code>类（无需显式注册），前提是此类与需处理的类位于同一个包，并且与该类具有相同的名称。并以<code>Editor</code>单词结尾。 可以使用以下类和包结构，这足以使<code>SomethingEditor</code>类被识别并用作<code>Something</code>类型属性的<code>PropertyEditor</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">com</span><br><span class="line">  chank</span><br><span class="line">    pop</span><br><span class="line">      Something</span><br><span class="line">      SomethingEditor &#x2F;&#x2F; the PropertyEditor for the Something class</span><br></pre></td></tr></table></figure>

<p>请注意，您也可以在此处使用标准 <code>BeanInfo</code> JavaBeans机制（ <a href="https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html" target="_blank" rel="noopener">这里</a>描述的是无关紧要的细节）。 以下示例使用<code>BeanInfo</code>机制使用关联类的属性显式注册一个或多个<code>PropertyEditor</code>实例：</p>
<p>com<br>  chank<br>    pop<br>      Something<br>      SomethingBeanInfo // the BeanInfo for the Something class</p>
<p>以下引用的<code>SomethingBeanInfo</code>类的Java源代码将<code>CustomNumberEditor</code>与<code>Something</code>类的<code>age</code>属性相关联：</p>
<pre><code>public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor(&quot;age&quot;, Something.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}</code></pre><p><a id="beans-beans-conversion-customeditor-registration"></a></p>
<h5 id="注册额外的自定义PropertyEditor"><a href="#注册额外的自定义PropertyEditor" class="headerlink" title="注册额外的自定义PropertyEditor"></a><a href="#beans-beans-conversion-customeditor-registration"></a>注册额外的自定义<code>PropertyEditor</code></h5><p>将bean属性设置为字符串值时，Spring IoC容器最终使用标准JavaBeans <code>PropertyEditor</code>实现将这些字符串转换为属性的复杂类型。 Spring预先注册了许多自定义<code>PropertyEditor</code>实现（例如，将表示为字符串的类名转换为<code>Class</code>对象）。 此外，Java的标准JavaBeans <code>PropertyEditor</code>查找机制允许对类的<code>PropertyEditor</code>进行适当的命名，并将其放置在与其提供支持的类相同的包中，以便可以自动找到它。</p>
<p>如果需要注册其他自定义<code>PropertyEditors</code>，可以使用多种机制。通常最麻烦也不推荐的策略是手动、简单的使用<code>ConfigurableBeanFactory</code>接口的<code>registerCustomEditor()</code>方法， 假设有一个<code>BeanFactory</code>引用，另一种（稍微更方便）机制是使用一个名为<code>CustomEditorConfigurer</code>的特殊bean工厂后置处理器。尽管您可以将bean工厂后置处理器与<code>BeanFactory</code>实现一起使用，但<code>CustomEditorConfigurer</code>具有嵌套属性设置， 因此我们强烈建议您将它与<code>ApplicationContext</code>一起使用，您可以在其中以类似的方式将其部署到任何其他bean以及它可以在哪里 自动检测并应用。</p>
<p>请注意，所有的bean工厂和应用程序上下文都自动使用了许多内置属性编辑器，在其内部都是使用<code>BeanWrapper</code>来进行属性转换的。 <code>BeanWrapper</code> 注册的标准属性编辑器列在<a href="#beans-beans-conversion">上一节</a>中 此外，<code>ApplicationContexts</code>还会覆盖或添加其他编辑器，以适合特定应用程序上下文类型的方式处理资源查找。</p>
<p>标准的 <code>PropertyEditor</code> JavaBeans实例用于将以字符串表示的属性值转换为属性的实际复杂类型。 <code>CustomEditorConfigurer</code>是一个bean后置处理工厂，可用于方便地在<code>ApplicationContext</code>中添加额外的 <code>PropertyEditor</code>实例。</p>
<p>请考虑以下示例，该示例定义名为<code>ExoticType</code>的用户类和另一个名为<code>DependsOnExoticType</code>的类，该类需要将<code>ExoticType</code>设置为属性：</p>
<pre><code>package example;

public class ExoticType {

    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {

    private ExoticType type;

    public void setType(ExoticType type) {
        this.type = type;
    }
}</code></pre><p>当创建好后，希望将type属性指定为一个字符串，<code>PropertyEditor</code>会在幕后将其转换成实际的<code>ExoticType</code>实例。以下bean定义显示了如何设置此关系：</p>
<pre><code>&lt;bean id=&quot;sample&quot; class=&quot;example.DependsOnExoticType&quot;&gt;
    &lt;property name=&quot;type&quot; value=&quot;aNameForExoticType&quot;/&gt;
&lt;/bean&gt;</code></pre><p><code>PropertyEditor</code>实现如下:</p>
<pre><code>// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}</code></pre><p>最后，以下示例显示如何使用<code>CustomEditorConfigurer</code>向<code>ApplicationContext</code>注册新的<code>PropertyEditor</code>，然后可以根据需要使用它：</p>
<pre><code>&lt;bean class=&quot;org.springframework.beans.factory.config.CustomEditorConfigurer&quot;&gt;
    &lt;property name=&quot;customEditors&quot;&gt;
        &lt;map&gt;
            &lt;entry key=&quot;example.ExoticType&quot; value=&quot;example.ExoticTypeEditor&quot;/&gt;
        &lt;/map&gt;
    &lt;/property&gt;
&lt;/bean&gt;</code></pre><p><a id="beans-beans-conversion-customeditor-registration-per"></a></p>
<h6 id="使用-PropertyEditorRegistrar"><a href="#使用-PropertyEditorRegistrar" class="headerlink" title="使用 PropertyEditorRegistrar"></a><a href="#beans-beans-conversion-customeditor-registration-per"></a>使用 <code>PropertyEditorRegistrar</code></h6><p>使用Spring容器注册属性编辑器的另一个策略是创建和使用<code>PropertyEditorRegistrar</code>。当需要在多种不同的情况下使用相同的属性编辑器集时，这个接口特别有用，编写相应的注册器并在每个案例中重用。 <code>PropertyEditorRegistrar</code>与另外一个称为<code>PropertyEditorRegistry</code>的接口一起工作。它使用Spring <code>BeanWrapper</code>(和<code>DataBinder</code>)实现。<code>PropertyEditorRegistrar</code>在与<code>CustomEditorConfigurer</code>(<a href="#beans-beans-conversion-customeditor-registration">本节</a>介绍的)一起使用时特别方便， 它公开<code>setPropertyEditorRegistrars(..)</code>的属性。<code>PropertyEditorRegistrar</code>和<code>CustomEditorConfigurer</code> 结合使用可以简单的在<code>DataBinder</code>和Spring MVC控制之间共享。 它避免了在自定义编辑器上进行同步的需要：<code>PropertyEditorRegistrar</code>需要为每个bean创建尝试创建新的<code>PropertyEditor</code>实例。</p>
<p>以下示例显示如何创建自己的<code>PropertyEditorRegistrar</code>实现:</p>
<pre><code>package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // you could register as many custom property editors as are required here...
    }
}</code></pre><p>有关<code>PropertyEditorRegistrar</code>实现的示例，另请参见<code>org.springframework.beans.support.ResourceEditorRegistrar</code>。 请注意，在实现<code>registerCustomEditors(..)</code> 方法时，它会创建每个属性编辑器的新实例。</p>
<p>下一个示例显示如何配置<code>CustomEditorConfigurer</code>并将 <code>CustomPropertyEditorRegistrar</code>的实例注入其中：</p>
<pre><code>&lt;bean class=&quot;org.springframework.beans.factory.config.CustomEditorConfigurer&quot;&gt;
    &lt;property name=&quot;propertyEditorRegistrars&quot;&gt;
        &lt;list&gt;
            &lt;ref bean=&quot;customPropertyEditorRegistrar&quot;/&gt;
        &lt;/list&gt;
    &lt;/property&gt;
&lt;/bean&gt;

&lt;bean id=&quot;customPropertyEditorRegistrar&quot;
    class=&quot;com.foo.editors.spring.CustomPropertyEditorRegistrar&quot;/&gt;</code></pre><p>最后（与本章的重点有所不同，对于那些使用<a href="web.html#mvc">Spring的MVC Web框架</a>的人来说），使用<code>PropertyEditorRegistrars</code>和数据绑定控制器（<code>SimpleFormController</code>）可以非常方便。 以下示例在 <code>initBinder(..)</code>方法的实现中使用 <code>PropertyEditorRegistrar</code>:</p>
<pre><code>public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}</code></pre><p>这种类型的code&gt;PropertyEditor注册方式可以让代码更加简洁（<code>initBinder(..)</code>的实现只有一行），并允许将通用<code>PropertyEditor</code>注册代码封装在一个类中，然后根据需要在尽可能多的控制器之间共享。</p>
<p><a id="core-convert"></a></p>
<h3 id="3-4-Spring-类型转换"><a href="#3-4-Spring-类型转换" class="headerlink" title="3.4. Spring 类型转换"></a><a href="#core-convert"></a>3.4. Spring 类型转换</h3><p>Spring 3引入了一个<code>core.convert</code>包，它提供了一个通用的类型转换系统。系统定义了一个用于实现类型转换逻辑的SPI和一个用于在运行时执行类型转换的API。 在Spring的容器中，此系统可以用作<code>PropertyEditor</code>的替代方法，它将外部bean属性值字符串转换为所需的属性类型。您还可以在需要进行类型转换的应用程序中的任何位置使用公共API。</p>
<p><a id="core-convert-Converter-API"></a></p>
<h4 id="3-4-1-SPI转换器"><a href="#3-4-1-SPI转换器" class="headerlink" title="3.4.1. SPI转换器"></a><a href="#core-convert-Converter-API"></a>3.4.1. SPI转换器</h4><p>实现类型转换逻辑的SPI是简易的，而且是强类型的。如以下接口定义所示：</p>
<pre><code>package org.springframework.core.convert.converter;

public interface Converter&lt;S, T&gt; {

    T convert(S source);
}</code></pre><p>创建自定义转换器都需要实现<code>Converter</code>接口，参数<code>S</code>是需要转换的类型，<code>T</code>是转换后的类型。这个转换器也可以应用在集合或数组上将<code>S</code>参数转换为<code>T</code>参数。前提是已经注册了委托数组或集合转换器（<code>DefaultConversionService</code>默认情况下也是如此）。 <code>Converter</code> interface and parameterize <code>S</code></p>
<p>对于要<code>convert(S)</code>的每个调用，源参数需保证不为null。转换失败时，<code>Converter</code>可能会引发任意的unchecked异常。具体来说，它应抛出<code>IllegalArgumentException</code>以报告无效的源值。 请注意确保您的Converter实现是线程安全的。</p>
<p>为方便起见，<code>core.convert.support</code>包中提供了几个转换器实现。 这些包括从字符串到数字和其他常见类型的转换器。 以下清单显示了<code>StringToInteger</code> 类，它是典型的<code>Converter</code> 实现：</p>
<pre><code>package org.springframework.core.convert.support;

final class StringToInteger implements Converter&lt;String, Integer&gt; {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}</code></pre><p><a id="core-convert-ConverterFactory-SPI)"></a></p>
<h4 id="3-4-2-使用-ConverterFactory"><a href="#3-4-2-使用-ConverterFactory" class="headerlink" title="3.4.2. 使用 ConverterFactory"></a><a href="#core-convert-ConverterFactory-SPI"></a>3.4.2. 使用 <code>ConverterFactory</code></h4><p>当需要集中整个类层次结构的转换逻辑时（例如，从String转换为java.lang.Enum对象时），您可以实现<code>ConverterFactory</code>，如以下示例所示：</p>
<pre><code>package org.springframework.core.convert.converter;

public interface ConverterFactory&lt;S, R&gt; {

    &lt;T extends R&gt; Converter&lt;S, T&gt; getConverter(Class&lt;T&gt; targetType);
}</code></pre><p>参数化S为您要转换的类型，R是需要转换后的类型的基类。 然后实现getConverter(Class<T>)，其中T是R的子类。</p>
<p>以<code>StringToEnum</code> <code>ConverterFactory</code>为例：</p>
<pre><code>package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory&lt;String, Enum&gt; {

    public &lt;T extends Enum&gt; Converter&lt;String, T&gt; getConverter(Class&lt;T&gt; targetType) {
        return new StringToEnumConverter(targetType);
    }

    private final class StringToEnumConverter&lt;T extends Enum&gt; implements Converter&lt;String, T&gt; {

        private Class&lt;T&gt; enumType;

        public StringToEnumConverter(Class&lt;T&gt; enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }
}</code></pre><p><a id="core-convert-GenericConverter-SPI"></a></p>
<h4 id="3-4-3-使用-GenericConverter"><a href="#3-4-3-使用-GenericConverter" class="headerlink" title="3.4.3. 使用 GenericConverter"></a><a href="#core-convert-GenericConverter-SPI"></a>3.4.3. 使用 <code>GenericConverter</code></h4><p>当您需要复杂的<code>Converter</code>实现时，请考虑使用<code>GenericConverter</code>接口。<code>GenericConverter</code>具有比<code>Converter</code>更灵活但不太强类型的签名，支持在多种源和目标类型之间进行转换。 此外，<code>GenericConverter</code>可以在实现转换逻辑时使用可用的源和目标字段上下文。 此上下文类允许通过字段注解或在字段签名上声明的一般信息来驱动类型转换。 以下清单显示了<code>GenericConverter</code>的接口定义：</p>
<pre><code>package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set&lt;ConvertiblePair&gt; getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}</code></pre><p>要实现<code>GenericConverter</code>，请使用<code>getConvertibleTypes()</code>返回支持的source→target类型对，然后实现 <code>convert(Object, TypeDescriptor, TypeDescriptor)</code>方法并编写转换逻辑。源<code>TypeDescriptor</code>提供对保存要转换的值的源字段的访问，目标<code>TypeDescriptor</code>提供对要设置转换值的目标字段的访问。</p>
<p>Java数组和集合之间转换的转换器是<code>GenericConverter</code>应用的例子。其中 <code>ArrayToCollectionConverter</code>内部声明目标集合类型用于解析集合元素类型的字段。 它允许在目标字段上设置集合之前，将源数组中的每个元素转换为集合元素类型。</p>
<p>因为<code>GenericConverter</code> 是一个更复杂的SPI接口，所以只有在需要时才应该使用它。 一般使用<code>Converter</code>或<code>ConverterFactory</code> 足以满足基本的类型转换需求。</p>
<p><a id="core-convert-ConditionalGenericConverter-SPI"></a></p>
<h5 id="Using-ConditionalGenericConverter"><a href="#Using-ConditionalGenericConverter" class="headerlink" title="Using ConditionalGenericConverter"></a><a href="#core-convert-ConditionalGenericConverter-SPI"></a>Using <code>ConditionalGenericConverter</code></h5><p>有时可能只想在特定条件为真时才执行<code>Converter</code>，例如，在特定注解的目标上使用<code>Converter</code>，或者，在一个特定的目标类方法（例如<code>static valueOf</code>方法）中执行<code>Converter</code>。 <code>ConditionalGenericConverter</code>是<code>GenericConverter</code> 和<code>ConditionalConverter</code>接口的组合。允许自定义匹配条件</p>
<pre><code>public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}</code></pre><p>用于持久实体标识符和实体引用之间转换的 <code>EntityConverter</code>是<code>ConditionalGenericConverter</code> 应用的例子。如果目标实体类型声明静态查找器方法(如<code>findAccount(Long)</code>), 那么<code>EntityConverter</code>只对匹配的生效。开发者可以实现<code>matches(TypeDescriptor, TypeDescriptor)</code>以执行finder方法来检查是否匹配。</p>
<p><a id="core-convert-ConversionService-API"></a></p>
<h4 id="3-4-4-The-ConversionService-API"><a href="#3-4-4-The-ConversionService-API" class="headerlink" title="3.4.4. The ConversionService API"></a><a href="#core-convert-ConversionService-API"></a>3.4.4. The <code>ConversionService</code> API</h4><p><code>ConversionService</code>定义了一个统一的API，用于在运行时执行类型转换逻辑。 转换器通常在以下Facade接口后面执行：</p>
<pre><code>package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class&lt;?&gt; sourceType, Class&lt;?&gt; targetType);

    &lt;T&gt; T convert(Object source, Class&lt;T&gt; targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}</code></pre><p>大多数 <code>ConversionService</code>实现还实现了<code>ConverterRegistry</code>，它提供了一个用于注册转换器的SPI。 在内部，<code>ConversionService</code>实现委托其注册的转换器执行类型转换逻辑。</p>
<p><code>core.convert.support</code>包中提供了强大的<code>ConversionService</code>实现。 <code>GenericConversionService</code>是适用于大多数环境的通用实现。 <code>ConversionServiceFactory</code>提供了一个方便的工厂，用于创建常见的<code>ConversionService</code>配置。</p>
<p><a id="core-convert-Spring-config"></a></p>
<h4 id="3-4-5-配置ConversionService"><a href="#3-4-5-配置ConversionService" class="headerlink" title="3.4.5. 配置ConversionService"></a><a href="#core-convert-Spring-config"></a>3.4.5. 配置<code>ConversionService</code></h4><p><code>ConversionService</code>是一个无状态对象，在应用程序启动时就会实例化，可以被多个线程共享。在Spring应用程序中，通常每个Spring容器(或<code>ApplicationContext</code>) 配置一个<code>ConversionService</code>实例。该<code>ConversionService</code>将被Spring获取，然后在框架需要执行类型转换时使用。也可以将<code>ConversionService</code>插入任意bean并直接调用它。</p>
<p>如果没有向Spring注册<code>ConversionService</code>，则使用基于<code>PropertyEditor</code>的原始系统。</p>
<p>要使用Spring注册默认的<code>ConversionService</code>，请添加以下bean定义，其 <code>id</code> 为<code>conversionService</code>：</p>
<pre><code>&lt;bean id=&quot;conversionService&quot;
    class=&quot;org.springframework.context.support.ConversionServiceFactoryBean&quot;/&gt;</code></pre><p>默认的<code>ConversionService</code>可以在字符串，数字，枚举，集合，映射和其他常见类型之间进行转换。 要使用您自己的自定义转换器补充或覆盖默认转换器，请设置<code>converters</code>属性。 属性值可以实现任何<code>Converter</code>, <code>ConverterFactory</code>, 或 <code>GenericConverter</code> 接口。</p>
<pre><code>&lt;bean id=&quot;conversionService&quot;
        class=&quot;org.springframework.context.support.ConversionServiceFactoryBean&quot;&gt;
    &lt;property name=&quot;converters&quot;&gt;
        &lt;set&gt;
            &lt;bean class=&quot;example.MyCustomConverter&quot;/&gt;
        &lt;/set&gt;
    &lt;/property&gt;
&lt;/bean&gt;</code></pre><p>在Spring MVC应用程序中使用<code>ConversionService</code>也很常见。 请参阅<a href="web.html#mvc-config-conversion">Spring MVC章节中的转换和格式化</a>。</p>
<p>在某些情况下，您可能希望在转换期间应用格式。 有关使用<code>FormattingConversionServiceFactoryBean</code>的详细信息，请参阅FormatterRegistry SPI。 <a href="#format-FormatterRegistry-SPI"><code>FormatterRegistry</code> SPI</a></p>
<p><a id="core-convert-programmatic-usage"></a></p>
<h4 id="3-4-6-编程使用ConversionService"><a href="#3-4-6-编程使用ConversionService" class="headerlink" title="3.4.6. 编程使用ConversionService"></a><a href="#core-convert-programmatic-usage"></a>3.4.6. 编程使用<code>ConversionService</code></h4><p>要以编程方式使用<code>ConversionService</code>实例，您可以像对任何其他bean一样注入对它的引用。 以下示例显示了如何执行此操作：</p>
<pre><code>@Service
public class MyService {

    @Autowired
    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}</code></pre><p>对于大多数用例，您可以使用指定targetType的<code>convert</code>方法，但它不适用于更复杂的类型，例如参数化元素的集合。 例如，如果想使用编程的方式将整数列表转换为字符串列表，则需要提供源和目标类型的正规定义。</p>
<p>幸运的是，<code>TypeDescriptor</code>提供了各种选项，使得这样做非常简单，如下例所示：</p>
<pre><code>DefaultConversionService cs = new DefaultConversionService();

List&lt;Integer&gt; input = ....
cs.convert(input,
    TypeDescriptor.forObject(input), // List&lt;Integer&gt; type descriptor
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));</code></pre><p>请注意， <code>DefaultConversionService</code>会自动注册适合大多数环境的转换器。 这包括集合转换器，基本类型转换器和基本的对象到字符串转换器。 您可以使用<code>DefaultConversionService</code>类上的静态<code>addDefaultConverters</code>方法向任何<code>ConverterRegistry</code> 注册相同的转换器。</p>
<p>值类型的转换器可以重用于数组和集合，因此无需创建特定的转换器即可将<code>S</code>的<code>Collection</code> 转换为<code>T</code>的<code>Collection</code>，前提是标准集合处理是合适的。 no need to create a specific converter to convert from a of to a of , assuming that standard collection handling is appropriate.</p>
<p><a id="format"></a></p>
<h3 id="3-5-Spring-字段格式化"><a href="#3-5-Spring-字段格式化" class="headerlink" title="3.5. Spring 字段格式化"></a><a href="#format"></a>3.5. Spring 字段格式化</h3><p>如前一节所述，<a href="#core-convert"><code>core.convert</code></a> 是一种通用类型转换系统。 它提供统一的<code>ConversionService</code>API以及强类型转换器SPI，用于实现从一种类型到另一种类型的转换逻辑。 Spring容器使用此系统绑定bean属性值。 此外，Spring Expression Language（SpEL）和 <code>DataBinder</code>都使用此系统绑定字段值。此外，当SpEL需要将 <code>Short</code>类型强转为 <code>Long</code>类型， 用于试图完成<code>expression.setValue(Object bean, Object value)</code>时，那么<code>core.convert</code>系统也可以提供这种功能。</p>
<p>现在考虑典型客户端环境（例如Web或桌面应用程序）的类型转换要求。在这种环境中,在这种环境中,还包括转换成为<code>String</code>用于支持视图呈现程序。此外，还通常需要本地化字符串值。 普通的转化器SPI没有提供按照直接进行格式转换的功能。更通用的 <code>core.convert</code> <code>Converter</code> SPI不能解决此类要求。为了实现这个功能，Spring 3添加了方便的<code>Formatter</code> SPI，它提供了简单强健的的 <code>PropertyEditor</code>专供客户端环境。</p>
<p>通常， 当需要使用通用类型转换时可以用<code>Converter</code>SPI。例如，在<code>java.util.Date</code>和 <code>java.lang.Long</code>之间进行转换。 在客户端环境（例如Web应用程序）中工作时，可以使用<code>Formatter</code> SPI， 并且需要解析和打印本地化的字段值。<code>ConversionService</code>为两个SPI提供统一的类型转换API。</p>
<p><a id="format-Formatter-SPI"></a></p>
<h4 id="3-5-1-Formatter-SPI"><a href="#3-5-1-Formatter-SPI" class="headerlink" title="3.5.1. Formatter SPI"></a><a href="#format-Formatter-SPI"></a>3.5.1. <code>Formatter</code> SPI</h4><p><code>Formatter</code> SPI实现字段格式化逻辑是简单的，强类型的。 以下清单显示了Formatter接口定义：</p>
<pre><code>package org.springframework.format;

public interface Formatter&lt;T&gt; extends Printer&lt;T&gt;, Parser&lt;T&gt; {
}</code></pre><p><code>Formatter</code>继承了 <code>Printer</code>和<code>Parser</code>内置的接口。以下清单显示了这两个接口的定义：</p>
<pre><code>public interface Printer&lt;T&gt; {

    String print(T fieldValue, Locale locale);
}

import java.text.ParseException;

public interface Parser&lt;T&gt; {

    T parse(String clientValue, Locale locale) throws ParseException;
}</code></pre><p>如果需要创建自定义的 <code>Formatter</code>，需要实现 <code>Formatter</code>接口。参数<code>T</code>类型是你需要格式化的类型。 例如，<code>java.util.Date</code>。实现<code>print()</code>操作在客户端本地设置中打印显示的<code>T</code>实例。 实现<code>parse()</code>操作以从客户端本地设置返回的格式化表示形式分析<code>T</code>的实例。如果尝试分析失败，<code>Formatter</code>会抛出<code>ParseException</code>或 <code>IllegalArgumentException</code>异常。注意确保自定义的<code>Formatter</code> 是线程安全的。</p>
<p><code>format</code>子包提供了多种<code>Formatter</code>实现方便使用。 <code>number</code>子包中提供了<code>NumberStyleFormatter</code>, <code>CurrencyStyleFormatter</code>, 和 <code>PercentStyleFormatter</code>用于格式化<code>java.lang.Number</code>（使用<code>java.text.NumberFormat</code>）。<code>datetime</code>子包中提供了<code>DateFormatter</code>用于格式化<code>java.util.Date</code>（使用<code>java.text.DateFormat</code>）。 <code>datetime.joda</code>包基于 <a href="http://joda-time.sourceforge.net" target="_blank" rel="noopener">Joda-Time 库</a>提供全面的日期时间格式支持。</p>
<p>以下<code>DateFormatter</code>是<code>Formatter</code>实现的示例：</p>
<pre><code>package org.springframework.format.datetime;

public final class DateFormatter implements Formatter&lt;Date&gt; {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return &quot;&quot;;
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}</code></pre><p>更多内容上Spring社区查看<code>Formatter</code>的版本信息，请参阅<a href="https://jira.spring.io/browse/SPR" target="_blank" rel="noopener">jira.spring.io</a>进行贡献。</p>
<p><a id="format-CustomFormatAnnotations"></a></p>
<h4 id="3-5-2-基于注解的格式化"><a href="#3-5-2-基于注解的格式化" class="headerlink" title="3.5.2. 基于注解的格式化"></a><a href="#format-CustomFormatAnnotations"></a>3.5.2. 基于注解的格式化</h4><p>字段格式也可以通过字段类型或注解进行配置。如果要将注解绑定到<code>Formatter</code>，请实现<code>AnnotationFormatterFactory</code>。以下清单显示了 <code>AnnotationFormatterFactory</code> 接口的定义：</p>
<pre><code>package org.springframework.format;

public interface AnnotationFormatterFactory&lt;A extends Annotation&gt; {

    Set&lt;Class&lt;?&gt;&gt; getFieldTypes();

    Printer&lt;?&gt; getPrinter(A annotation, Class&lt;?&gt; fieldType);

    Parser&lt;?&gt; getParser(A annotation, Class&lt;?&gt; fieldType);
}</code></pre><p>参数化A是将格式逻辑与(例如<code>org.springframework.format.annotation.DateTimeFormat</code>关联到字段<code>annotationType</code>。 <code>getFieldTypes()</code>返回注解可用的字段类型。 使<code>getPrinter()</code>返回<code>Printer</code>以打印注解字段值。<code>getParser()</code>返回一个 <code>Parser</code>以分析注解字段的<code>clientValue</code>。</p>
<p>下面的示例 <code>AnnotationFormatterFactory</code>实现，将<code>@NumberFormat</code>注解绑定到格式化程序。此注解允许指定数字样式或模式</p>
<pre><code>public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory&lt;NumberFormat&gt; {

    public Set&lt;Class&lt;?&gt;&gt; getFieldTypes() {
        return new HashSet&lt;Class&lt;?&gt;&gt;(asList(new Class&lt;?&gt;[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer&lt;Number&gt; getPrinter(NumberFormat annotation, Class&lt;?&gt; fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser&lt;Number&gt; getParser(NumberFormat annotation, Class&lt;?&gt; fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter&lt;Number&gt; configureFormatterFrom(NumberFormat annotation, Class&lt;?&gt; fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}</code></pre><p>想要触发格式化，只需在在字段上添加<code>@NumberFormat</code>注解即可。</p>
<pre><code>public class MyModel {

    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}</code></pre><p><a id="format-annotations-api"></a></p>
<h5 id="格式化注解API"><a href="#格式化注解API" class="headerlink" title="格式化注解API"></a><a href="#format-annotations-api"></a>格式化注解API</h5><p><code>org.springframework.format.annotation</code>包中存在可移植格式注解API。 您可以使用<code>@NumberFormat</code>格式化java.lang.Number字段， 使用<code>@DateTimeFormat</code>格式化<code>java.util.Date</code>， <code>java.util.Calendar</code>，<code>java.util.Long</code>或Joda-Time字段。</p>
<p>下面的示例使用<code>@DateTimeFormat</code>将<code>java.util.Date</code> 化为ISO Date（yyyy-MM-dd）：</p>
<pre><code>public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}</code></pre><p><a id="format-FormatterRegistry-SPI"></a></p>
<h4 id="3-5-3-FormatterRegistry-SPI"><a href="#3-5-3-FormatterRegistry-SPI" class="headerlink" title="3.5.3. FormatterRegistry SPI"></a><a href="#format-FormatterRegistry-SPI"></a>3.5.3. <code>FormatterRegistry</code> SPI</h4><p><code>FormatterRegistry</code>是一个用于注册格式化程序和转换器的SPI。 <code>FormattingConversionService</code>适用于大多数环境的<code>FormatterRegistry</code>实现。此实现可以通过编程或以声明的方式配置为可用<code>FormattingConversionServiceFactoryBean</code>的Spring bean。 由于它也实现了<code>ConversionService</code>，因此可以直接配置用于Spring的 <code>DataBinder</code>和Spring的表达式语言（SpEL）。</p>
<p>以下清单显示了<code>FormatterRegistry</code>:</p>
<pre><code>package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class&lt;?&gt; fieldType, Printer&lt;?&gt; printer, Parser&lt;?&gt; parser);

    void addFormatterForFieldType(Class&lt;?&gt; fieldType, Formatter&lt;?&gt; formatter);

    void addFormatterForFieldType(Formatter&lt;?&gt; formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory&lt;?, ?&gt; factory);
}</code></pre><p>如上所示, Formatters通过fieldType或注解进行注册。</p>
<p><code>FormatterRegistry</code> SPI可以集中配置格式规则，避免跨控制器的重复配置。例如，想要强制所有日期字段都以特定方式格式化，或者具有特定注解的字段以某种方式格式化。 使用共享的<code>FormatterRegistry</code>，开发者只需一次定义这些规则，即可到处使用。</p>
<p><a id="format-FormatterRegistrar-SPI"></a></p>
<h4 id="3-5-4-FormatterRegistrar-SPI"><a href="#3-5-4-FormatterRegistrar-SPI" class="headerlink" title="3.5.4. FormatterRegistrar SPI"></a><a href="#format-FormatterRegistrar-SPI"></a>3.5.4. <code>FormatterRegistrar</code> SPI</h4><p><code>FormatterRegistrar</code> 是用于注册格式化器和通过FormatterRegistry转换的SPI:</p>
<pre><code>package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}</code></pre><p><code>FormatterRegistrar</code>用于注册多个相关的转换器或格式化器（根据给定的格式化目录注册，例如Date格式化）。在直接注册不能实现时FormatterRegistrar就派上用场了， 例如，当格式化程序需要在不同于其自身<code>&lt;T&gt;</code>的特定字段类型下进行索引时，或者在注册<code>Printer</code>/<code>Parser</code>对时。下一节提供了有关转换器和格式化器注册的更多信息。</p>
<p><a id="format-configuring-formatting-mvc"></a></p>
<h4 id="3-5-5-在Spring-MVC中配置格式化"><a href="#3-5-5-在Spring-MVC中配置格式化" class="headerlink" title="3.5.5. 在Spring MVC中配置格式化"></a><a href="#format-configuring-formatting-mvc"></a>3.5.5. 在Spring MVC中配置格式化</h4><p>请参阅Spring MVC章节中的<a href="web.html#mvc-config-conversion">转换和格式化</a>。</p>
<p><a id="format-configuring-formatting-globaldatetimeformat"></a></p>
<h3 id="3-6-配置全局日期和时间格式"><a href="#3-6-配置全局日期和时间格式" class="headerlink" title="3.6. 配置全局日期和时间格式"></a><a href="#format-configuring-formatting-globaldatetimeformat"></a>3.6. 配置全局日期和时间格式</h3><p>默认情况下，不带 <code>@DateTimeFormat</code>注解的日期和时间字段使用<code>DateFormat.SHORT</code>（短日期）的格式转换字符串。开发者也可以使用自定义的全局格式覆盖默认格式。</p>
<p>此时需要确保Spring不注册默认格式化器，而应该手动注册所有格式化器，根据是否使用joda时间库，可以选择使用<code>org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar</code> or <code>org.springframework.format.datetime.DateFormatterRegistrar</code> 类。</p>
<p>例如，以下Java配置注册全局<code>yyyyMMdd</code>格式（此示例不依赖于Joda-Time库）：</p>
<pre><code>@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter(&quot;yyyyMMdd&quot;));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}</code></pre><p>如果您更喜欢基于XML的配置，则可以使用 <code>FormattingConversionServiceFactoryBean</code>。 以下示例显示了如何执行此操作（这次使用Joda Time）:</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
    xsi:schemaLocation=&quot;
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd&gt;

    &lt;bean id=&quot;conversionService&quot; class=&quot;org.springframework.format.support.FormattingConversionServiceFactoryBean&quot;&gt;
        &lt;property name=&quot;registerDefaultFormatters&quot; value=&quot;false&quot; /&gt;
        &lt;property name=&quot;formatters&quot;&gt;
            &lt;set&gt;
                &lt;bean class=&quot;org.springframework.format.number.NumberFormatAnnotationFormatterFactory&quot; /&gt;
            &lt;/set&gt;
        &lt;/property&gt;
        &lt;property name=&quot;formatterRegistrars&quot;&gt;
            &lt;set&gt;
                &lt;bean class=&quot;org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar&quot;&gt;
                    &lt;property name=&quot;dateFormatter&quot;&gt;
                        &lt;bean class=&quot;org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean&quot;&gt;
                            &lt;property name=&quot;pattern&quot; value=&quot;yyyyMMdd&quot;/&gt;
                        &lt;/bean&gt;
                    &lt;/property&gt;
                &lt;/bean&gt;
            &lt;/set&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
&lt;/beans&gt;</code></pre><p>Joda-Time提供单独的不同类型来表示<code>date</code>, <code>time</code>, 和 <code>date-time</code>。<code>JodaTimeFormatterRegistrar</code>的<code>dateFormatter</code>, <code>timeFormatter</code>, 和 <code>dateTimeFormatter</code> 属性应用于为每种类型配置不同的格式。<code>DateTimeFormatterFactoryBean</code>提供了一种创建格式化器的快捷方法。</p>
<p>如果使用Spring MVC框架，请记住显式配置使用的转换服务。对于基于Java的<code>@Configuration</code>，这意味着继承<code>WebMvcConfigurationSupport</code>类并覆盖了<code>mvcConversionService()</code>。 对于XML，您应该使用 <code>mvc:annotation-driven</code> 元素的<code>conversion-service</code>属性。 有关详细信息，请参阅<a href="web.html#mvc-config-conversion">转换和格式。</a></p>
<p><a id="validation-beanvalidation"></a></p>
<h3 id="3-7-Spring的验证"><a href="#3-7-Spring的验证" class="headerlink" title="3.7. Spring的验证"></a><a href="#validation-beanvalidation"></a>3.7. Spring的验证</h3><p>Spring 3介绍了对其验证支持的几个改进，首先，完全支持JSR-303 Bean Validation API。 其次，当以编程方式使用时，Spring的<code>DataBinder</code>可以验证对象以及绑定它们。 第三，Spring MVC支持声明性地验证<code>@Controller</code> 输入。</p>
<p><a id="validation-beanvalidation-overview"></a></p>
<h4 id="3-7-1-JSR-303的bean-Validation-API的总览"><a href="#3-7-1-JSR-303的bean-Validation-API的总览" class="headerlink" title="3.7.1.JSR-303的bean Validation API的总览"></a><a href="#validation-beanvalidation-overview"></a>3.7.1.JSR-303的bean Validation API的总览</h4><p>JSR-303是Java平台标准化验证约束声明和元数据的标准API，使用此API，可以使用声明性验证约束对域模型属性进行注解，并且在运行时强制执行。开发者可以利用一些内置约束，也可于自定义约束。</p>
<p>请考虑以下示例，该示例显示了具有两个属性的简单<code>PersonForm</code> 模型:</p>
<pre><code>public class PersonForm {
    private String name;
    private int age;
}</code></pre><p>JSR-303允许您为这些属性定义声明性验证约束，如以下示例所示:</p>
<pre><code>public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}</code></pre><p>当JSR-303 Validator验证此类的实例时，将强制执行这些约束。</p>
<p>有关JSR-303和JSR-349的一般信息，请参阅<a href="http://beanvalidation.org/" target="_blank" rel="noopener">Bean Validation website</a>网站。有关默认引用实现的特定功能的信息，请参阅Hibernate Validator <a href="https://www.hibernate.org/412.html" target="_blank" rel="noopener">Hibernate Validator</a> 文档。要了解如何将bean验证提供程序设置为Spring bean，请继续阅读以下内容。 bean, keep reading.</p>
<p><a id="validation-beanvalidation-spring"></a></p>
<h4 id="3-7-2-配置bean-Validation提供者"><a href="#3-7-2-配置bean-Validation提供者" class="headerlink" title="3.7.2. 配置bean Validation提供者"></a><a href="#validation-beanvalidation-spring"></a>3.7.2. 配置bean Validation提供者</h4><p>Spring提供了对Bean Validation API的完全支持，包括对以 JSR-303 或 JSR-349 Bean Validation提供作为Spring bean进行引导的快捷支持。允许在应用程序需要验证的任何地方注入<code>javax.validation.ValidatorFactory</code> 或 <code>javax.validation.Validator</code>。</p>
<p>您可以使用<code>LocalValidatorFactoryBean</code> 将默认Validator配置为Spring bean，如以下示例所示：</p>
<pre><code>&lt;bean id=&quot;validator&quot;
    class=&quot;org.springframework.validation.beanvalidation.LocalValidatorFactoryBean&quot;/&gt;</code></pre><p>上面的基本配置将触发Bean验证以使用其默认的引导机制进行初始化，JSR-303或JSR-349提供程序（例如Hibernate Validator）应该存在于类路径中并自动检测。</p>
<p><a id="validation-beanvalidation-spring-inject"></a></p>
<h5 id="注入Validator"><a href="#注入Validator" class="headerlink" title="注入Validator"></a><a href="#validation-beanvalidation-spring-inject"></a>注入Validator</h5><p><code>LocalValidatorFactoryBean</code>实现了<code>javax.validation.ValidatorFactory</code>和<code>javax.validation.Validator</code>，以及Spring的<code>org.springframework.validation.Validator</code>。 您可以将这些接口中的任何一个引用注入到需要调用验证逻辑的bean中。</p>
<p>如果您希望直接使用Bean Validation API，则可以注入对<code>javax.validation.Validator</code>的引用，如以下示例所示：</p>
<pre><code>import javax.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;</code></pre><p>如果您的bean需要Spring Validation API，则可以注入对<code>org.springframework.validation.Validator</code>的引用，如以下示例所示：</p>
<pre><code>import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}</code></pre><p><a id="validation-beanvalidation-spring-constraints"></a></p>
<h5 id="配置自定义约束"><a href="#配置自定义约束" class="headerlink" title="配置自定义约束"></a><a href="#validation-beanvalidation-spring-constraints"></a>配置自定义约束</h5><p>每个Bean验证约束都由两部分组成：首先是声明约束及其可配置属性的<code>@Constraint</code>注解，然后是实现约束行为的<code>javax.validation.ConstraintValidator</code>接口实现。</p>
<p>如果要将声明与实现关联，每个 <code>@Constraint</code>注解都会引用相应的<code>ConstraintValidator</code>实现类。在运行中，当在域模型中遇到约束注解时，<code>ConstraintValidatorFactory</code>会将引用的实现实例化。</p>
<p>默认情况下，<code>LocalValidatorFactoryBean</code>会配置<code>SpringConstraintValidatorFactory</code>，它会使用Spring去创建<code>ConstraintValidator</code>实例。这允许自定义<code>ConstraintValidators</code>， 就像任何其他Spring bean一样，从依赖注入中获益。</p>
<p>下面是自定义<code>@Constraint</code>声明的例子，使用Spring的依赖注入来管理<code>ConstraintValidator</code>的实现:</p>
<pre><code>@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}

import javax.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

    @Autowired;
    private Foo aDependency;

    ...
}</code></pre><p>如前面的示例所示，<code>ConstraintValidator</code>实现可以将其依赖项<code>@Autowired</code>与任何其他Spring bean一样。</p>
<p><a id="validation-beanvalidation-spring-method"></a></p>
<h5 id="Spring驱动的方法验证"><a href="#Spring驱动的方法验证" class="headerlink" title="Spring驱动的方法验证"></a><a href="#validation-beanvalidation-spring-method"></a>Spring驱动的方法验证</h5><p>Bean Validation 1.1支持的方法验证，Hibernate Validator 4.3支持的自定义扩展都可以通过 <code>MethodValidationPostProcessor</code> 定义集成到Spring上下文中。如下所示：:</p>
<pre><code>&lt;bean class=&quot;org.springframework.validation.beanvalidation.MethodValidationPostProcessor&quot;/&gt;</code></pre><p>为了符合Spring驱动方法验证的条件，所有目标类都需要使用Spring的<code>@Validated</code>进行注解，还可以选择声明要使用的验证组。 使用Hibernate Validator和Bean Validation 1.1提供验证的步骤可以查看<a href="https://docs.spring.io/spring-framework/docs/5.1.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.html" target="_blank" rel="noopener"><code>MethodValidationPostProcessor</code></a>的javadocs</p>
<p><a id="validation-beanvalidation-spring-other"></a></p>
<h5 id="额外的配置选项"><a href="#额外的配置选项" class="headerlink" title="额外的配置选项"></a><a href="#validation-beanvalidation-spring-other"></a>额外的配置选项</h5><p>对于大多数情况，默认的<code>LocalValidatorFactoryBean</code>配置就足够了。从消息插入到遍历解析，各种Bean Validation构造有许多配置选项。 有关这些选项的更多信息，请参阅<a href="https://docs.spring.io/spring-framework/docs/5.1.3.BUILD-SNAPSHOT/javadoc-api/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.html" target="_blank" rel="noopener"><code>LocalValidatorFactoryBean</code></a> javadoc。</p>
<p><a id="validation-binder"></a></p>
<h4 id="3-7-3-配置-DataBinder"><a href="#3-7-3-配置-DataBinder" class="headerlink" title="3.7.3. 配置 DataBinder"></a><a href="#validation-binder"></a>3.7.3. 配置 <code>DataBinder</code></h4><p>从Spring 3开始，您可以使用<code>Validator</code>配置<code>DataBinder</code>实例。 配置完成后，您可以通过调用<code>binder.validate()</code>来调用 <code>Validator</code>。 任何验证 <code>Errors</code>都会自动添加到活页夹的<code>BindingResult</code>中。</p>
<p>以下示例说明如何在绑定到目标对象后以编程方式使用<code>DataBinder</code>来调用验证逻辑：</p>
<pre><code>Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();</code></pre><p><code>DataBinder</code>还可以通过 <code>dataBinder.addValidators</code>和<code>dataBinder.replaceValidators</code>来配置多个<code>Validator</code>实例。 将全局配置的Bean Validation与本地在<code>DataBinder</code>实例上配置的Spring <code>Validator</code>相结合，这非常有用。 请参阅<a href="#validation-mvc-configuring">[validation-mvc-configuring]</a>。</p>
<p><a id="validation-mvc"></a></p>
<h4 id="3-7-4-Spring-MVC-3-验证"><a href="#3-7-4-Spring-MVC-3-验证" class="headerlink" title="3.7.4. Spring MVC 3 验证"></a><a href="#validation-mvc"></a>3.7.4. Spring MVC 3 验证</h4><p>See <a href="web.html#mvc-config-validation">Validation</a> in the Spring MVC chapter.</p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://dxysun.com/2020/12/13/springForvalidator/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/spring/" rel="tag">spring</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/12/13/springForSpel/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            4、Spring 表达式语言(SpEL)
          
        </div>
      </a>
    
    
      <a href="/2020/12/13/springForResources/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">2、Spring 资源</div>
      </a>
    
  </nav>

  
   
  
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2024
        <i class="ri-heart-fill heart_icon"></i> dxysun
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
        <li>
          <a href="https://beian.miit.gov.cn" target="_black" rel="nofollow">豫ICP备17012675号-1</a>
        </li>
        
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="https://dxysun.com/static/logo.png" alt="迎着朝阳"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/photos">相册</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/alipay-20201219151322.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/weixin-20201219151346.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
  var ayerConfig = {
    mathjax: true
  }
</script>

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


<script src="/js/dz.js"></script>



    
  </div>
</body>

</html>